/*******************************************************************************
** 文件名称：gm_multi_key_.h
** 文件作用：多功能按键
** 创建作者：Tom Free 付瑞彪
** 创建时间：2020-05-27
** 文件备注：此文件是多功能按键的头文件，主要包括API声明和数据类型定义
**
** 更新记录：
**          2020-05-27 -> 创建文件
**                                                          <Tom Free 付瑞彪>
**          2020-12-27 -> 修改代码风格为linux风格，取消自定义类型依赖，
**                        提供错误返回码标识，让使用者更加清楚错误的原因
**                                                          <Tom Free 付瑞彪>
**          2020-12-28 -> 修改代码部分命名，取消冗余的枚举，增加详细注释
**                                                          <Tom Free 付瑞彪>
**
**              Copyright (c) 2018-2020 付瑞彪 All Rights Reserved
**
**       1 Tab == 4 Spaces     UTF-8     ANSI C Language(C99)
*******************************************************************************/
#ifndef __GM_MULTI_KEY_H__
#define __GM_MULTI_KEY_H__

#include "gm_multi_key_cfg.h"
#include "stdint.h"

/* ms转ticks，用于计算相关ticks参数 */
#define GM_MULTI_KEY_MS_TO_TICKS(ms)    ((ms) / (GM_MULTI_KEY_POLL_INTERVAL))

/* 操作结果定义，用于操作的返回提示 */
typedef enum _gm_multi_key_result_t
{
    /* 成功或正确 */
    GM_MULTI_KEY_RESULT_OK = 0,
    /* 传入参数错误，例如指针为空 */
    GM_MULTI_KEY_RESULT_ARGS_ERR = -1,
    /* 按键对象已存在于链表中，添加时发现已经在链表了 */
    GM_MULTI_KEY_RESULT_EXIST = -2,
    /* 未在链表中找到按键对象，删除时未在链表中找到对象 */
    GM_MULTI_KEY_RESULT_NOT_FOUND = -3,
} gm_multi_key_result_t;

/* 按键电平定义，抽象电平概念，用户硬件可以使用任意的电平，包括ADC按键，
 * 只需要提供回调函数是按照下面的抽象电平传入即可，达到高度自定义和扩展 */
typedef enum _gm_multi_key_level_t
{
    /* 按下， */
    GM_MULTI_KEY_LEVEL_PRESS,
    /* 释放 */
    GM_MULTI_KEY_LEVEL_RELEASE,
} gm_multi_key_level_t;

/* 按键事件，标识当前发生的按键事件，
 * 用户可按需处理，只需要关心需求的部分事件 */
typedef enum _gm_multi_key_event_t
{
    /* 无事件 */
    GM_MULTI_KEY_EVENT_NONE,
    /* 按下事件，按下边沿触发 */
    GM_MULTI_KEY_EVENT_PRESS,
    /* 释放事件，松开边沿触发 */
    GM_MULTI_KEY_EVENT_RELEASE,
    /* 首次触发长按，按下达到长按时间时触发 */
    GM_MULTI_KEY_EVENT_LONG_FIRST,
    /* 重复按下，按下时一直触发 */
    GM_MULTI_KEY_EVENT_REPEAT,
    /* 单击，短暂按下并松开 */
    GM_MULTI_KEY_EVENT_SINGLE_CLICK,
    /* 双击，因为常用，所以单独列一个，其实可以归为连击 */
    GM_MULTI_KEY_EVENT_DOUBLE_CLICK,
    /* 连击，短暂的连续点击 */
    GM_MULTI_KEY_EVENT_MULTI_CLICK,
    /* 事件数量总数 */
    GM_MULTI_KEY_EVENT_NUM,
} gm_multi_key_event_t;

/* 定义按键类型体，推荐采用初始化函数来初始化此对象实例，
 * 不要直接进行值操作，除非对此系统的实现源码特别理解 */
typedef struct _gm_multi_key_t gm_multi_key_t;

/* 读取IO口电平回调函数，用户按照此函数模板去实现回调 */
typedef gm_multi_key_level_t gm_multi_key_read_io_cb_t(gm_multi_key_t *p_key);
/* 按键事件处理回调，用户按照此函数模板去实现回调 */
typedef int gm_multi_key_event_cb_t(gm_multi_key_t *p_key);

/* 按键结构定义 */
struct _gm_multi_key_t
{
    /* 链表指针，内部管理器使用，不可写，只可读 */
    struct _gm_multi_key_t    *next;
    /* IO读取函数，用户传入，需保证合法性，可读写，建议调用函数写入 */
    gm_multi_key_read_io_cb_t *read_io_cb;
    /* 事件处理回调，用户传入，需保证合法性，可读写，建议调用函数写入 */
    gm_multi_key_event_cb_t   *event_proc_cb;
    /* 连击最大次数，超过此数量不再连击计数，
     * 以设置的扫描间隔为基准，可读写，>=1，建议调用函数写入 */
    uint8_t                    click_cnt_max;
    /* 消抖滴答个数，每次读取电平必须保证此时间内的数据稳定才进入状态机，
     * 以设置的扫描间隔为基准，可读写，>=1，建议调用函数写入 */
    uint8_t                    debounce_ticks;
    /* 连击间隔滴答个数，时间在此范围内的多击才有效，超时此次连击结束，
     * 以设置的扫描间隔为基准，可读写，>=1，建议调用函数写入 */
    uint16_t                   hit_again_ticks;
    /* 长按首次触发间隔滴答个数，第一次进入长按的时间超过此时间触发长按，
     * 以设置的扫描间隔为基准，可读写，>=1，建议调用函数写入 */
    uint16_t                   long_press_ticks;
    /* 长按重复触发间隔滴答个数，触发长按后不松手，
     * 按照此时间间隔产生重复按下事件，
     * 以设置的扫描间隔为基准，可读写，>=1，建议调用函数写入 */
    uint16_t                   long_repeat_ticks;
    /* 按键内部计数器，不可写，只可读 */
    uint16_t                   internal_cnt;
    /* 按键内部消抖计数，不可写，只可读 */
    uint8_t                    debounce_cnt;
    /* 连续点击计数，用户可以读取此值来判断连击次数，不可写，只可读 */
    uint8_t                    click_cnt;
    /* 按键状态机状态，内部枚举定义，不可写，只可读 */
    uint8_t                    fsm_status;
    /* 按键事件，用户可以读取此值来处理事件，不可写，只可读 */
    gm_multi_key_event_t       event;
    /* IO电平，内部使用，不可写，只可读 */
    gm_multi_key_level_t       level;
};

#ifdef __cplusplus
extern "C" {
#endif  /* __cplusplus */

/*******************************************************************************
** 函数名称：gm_multi_key_mgr_init
** 函数作用：按键管理器初始化
** 输入参数：无
** 输出参数：无
** 使用样例：gm_multi_key_mgr_init();
** 函数备注：可以不使用此函数，此函数要在其他函数之前调用，
**           主要用到低功耗内存会掉电的情况下重新设置内存初始值
*******************************************************************************/
void gm_multi_key_mgr_init(void);

/*******************************************************************************
** 函数名称：gm_multi_key_init
** 函数作用：初始化按键
** 输入参数：p_key      - 按键指针对象
**           read_io_cb - 按键读取IO电平函数指针
**           event_cb   - 事件回调函数
** 输出参数：操作结果
** 使用样例：gm_multi_key_init(&key1, key1_read, key1_enevt);
** 函数备注：此函数会读取一次电平存放起来，作为初始化电平，
**           请在此函数调用之前初始化IO外设；
**           此函数只初始化回调函数指针，其它参数使用默认值，需要设置请采用相应函数
*******************************************************************************/
gm_multi_key_result_t gm_multi_key_init(gm_multi_key_t *p_key,
                                        gm_multi_key_read_io_cb_t *read_io_cb,
                                        gm_multi_key_event_cb_t *event_cb);

/*******************************************************************************
** 函数名称：gm_multi_key_set_read_io_cb
** 函数作用：设置读取IO回调
** 输入参数：p_key      - 按键指针对象
**           read_io_cb - 按键读取IO电平函数指针
** 输出参数：操作结果
** 使用样例：gm_multi_key_result_t res = gm_multi_key_set_read_io_cb(&key1, key1_read);
** 函数备注：
*******************************************************************************/
gm_multi_key_result_t gm_multi_key_set_read_io_cb(gm_multi_key_t *p_key,
                                        gm_multi_key_read_io_cb_t *read_io_cb);

/*******************************************************************************
** 函数名称：gm_multi_key_set_event_cb
** 函数作用：设置事件处理回调
** 输入参数：p_key      - 按键指针对象
**           event_cb   - 事件回调函数
** 输出参数：操作结果
** 使用样例：gm_multi_key_result_t res = gm_multi_key_set_event_cb(&key1, key1_enevt);
** 函数备注：
*******************************************************************************/
gm_multi_key_result_t gm_multi_key_set_event_cb(gm_multi_key_t *p_key,
                                        gm_multi_key_event_cb_t *event_cb);

/*******************************************************************************
** 函数名称：gm_multi_key_set_click_cnt_max
** 函数作用：设置连击最大次数
** 输入参数：p_key         - 按键指针对象
**           click_cnt_max - 连击最大次数
** 输出参数：操作结果
** 使用样例：gm_multi_key_result_t res = gm_multi_key_set_click_cnt_max(&key1, 10);
** 函数备注：
*******************************************************************************/
gm_multi_key_result_t gm_multi_key_set_click_cnt_max(gm_multi_key_t *p_key,
                                        uint8_t click_cnt_max);

/*******************************************************************************
** 函数名称：gm_multi_key_set_debounce_ticks
** 函数作用：设置防抖周期
** 输入参数：p_key          - 按键指针对象
**           debounce_ticks - 防抖周期
** 输出参数：操作结果
** 使用样例：gm_multi_key_result_t res = gm_multi_key_set_debounce_ticks(&key1, 10);
** 函数备注：
*******************************************************************************/
gm_multi_key_result_t gm_multi_key_set_debounce_ticks(gm_multi_key_t *p_key,
                                        uint8_t debounce_ticks);

/*******************************************************************************
** 函数名称：gm_multi_key_set_hit_again_ticks
** 函数作用：设置连击最大间隔时间周期
** 输入参数：p_key           - 按键指针对象
**           hit_again_ticks - 连击间隔最大周期
** 输出参数：操作结果
** 使用样例：gm_multi_key_result_t res = gm_multi_key_set_hit_again_ticks(&key1, 10);
** 函数备注：
*******************************************************************************/
gm_multi_key_result_t gm_multi_key_set_hit_again_ticks(gm_multi_key_t *p_key,
                                        uint8_t hit_again_ticks);

/*******************************************************************************
** 函数名称：gm_multi_key_set_long_press_ticks
** 函数作用：设置长按触发时间周期
** 输入参数：p_key            - 按键指针对象
**           long_press_ticks - 长按触发时间周期
** 输出参数：操作结果
** 使用样例：gm_multi_key_result_t res = gm_multi_key_set_long_press_ticks(&key1, 10);
** 函数备注：
*******************************************************************************/
gm_multi_key_result_t gm_multi_key_set_long_press_ticks(gm_multi_key_t *p_key,
                                        uint8_t long_press_ticks);

/*******************************************************************************
** 函数名称：gm_multi_key_set_long_repeat_ticks
** 函数作用：设置长按重复按下事件触发时间周期
** 输入参数：p_key             - 按键指针对象
**           long_repeat_ticks - 重复按下事件触发时间周期
** 输出参数：操作结果
** 使用样例：gm_multi_key_result_t res = gm_multi_key_set_long_repeat_ticks(&key1, 10);
** 函数备注：
*******************************************************************************/
gm_multi_key_result_t gm_multi_key_set_long_repeat_ticks(gm_multi_key_t *p_key,
                                        uint8_t long_repeat_ticks);

/*******************************************************************************
** 函数名称：gm_multi_key_add
** 函数作用：添加一个按键进入链表中
** 输入参数：p_key - 按键指针对象
** 输出参数：操作结果
** 使用样例：gm_multi_key_result_t res = gm_multi_key_add(&key1);
** 函数备注：pkey非法或已存在链表均会返回失败
*******************************************************************************/
gm_multi_key_result_t gm_multi_key_add(gm_multi_key_t* p_key);

/*******************************************************************************
** 函数名称：gm_multi_key_remove
** 函数作用：从按键链表中移除一个按键对象
** 输入参数：p_key - 按键指针对象
** 输出参数：操作结果
** 使用样例：gm_multi_key_result_t res = gm_multi_key_remove(&key1);
** 函数备注：pkey非法或不存在均会返回失败
*******************************************************************************/
gm_multi_key_result_t gm_multi_key_remove(gm_multi_key_t* p_key);

/*******************************************************************************
** 函数名称：gm_multi_key_poll
** 函数作用：执行按键事件轮询
** 输入参数：无
** 输出参数：无
** 使用样例：gm_multi_key_poll();
** 函数备注：此函数会依次每个按键进行轮询，有事件会执行相应事件的回调函数；
**           需要放到定期执行的定时器回调或者定时任务中，尽量不要放到中断中；
**           如果需要放到中断中，请对上面的按键初始化以及链表操作的函数进行
**           临界段保护，且执行事件的回调函数体尽量短小，当然中间使用了全局
**           或静态变量也要注意临界段保护
*******************************************************************************/
void gm_multi_key_poll(void);

#ifdef __cplusplus
}
#endif  /* __cplusplus */

#endif  /* __GM_MULTI_KEY_H__ */
